/*-------------------------------------------------------------------------
 *
 * proc.c
 *	  管理每个进程的共享内存数据结构的例程
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/storage/lmgr/proc.c
 *
 *-------------------------------------------------------------------------
 */
/*
 * 接口 (a):
 *		ProcSleep(), ProcWakeup(),
 *		ProcQueueAlloc() -- 为睡眠进程创建一个共享内存队列
 *		ProcQueueInit() -- 创建一个不分配内存的队列
 *
 * 等待锁会导致后端进入休眠状态。释放锁的进程会再次唤醒该进程（并给予其错误代码，以便它知道是否在错误条件下被唤醒）。
 *
 * 接口 (b):
 *
 * ProcReleaseLocks -- 释放与当前事务相关的锁
 *
 * ProcKill -- 销毁与该进程相关的共享内存状态（和锁）。
 */
#include "postgres.h"

#include <signal.h>
#include <unistd.h>
#include <sys/time.h>

#include "access/transam.h"
#include "access/twophase.h"
#include "access/xlogutils.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "replication/slot.h"
#include "replication/syncrep.h"
#include "replication/walsender.h"
#include "storage/condition_variable.h"
#include "storage/ipc.h"
#include "storage/lmgr.h"
#include "storage/pmsignal.h"
#include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "storage/spin.h"
#include "storage/standby.h"
#include "utils/timeout.h"
#include "utils/timestamp.h"

/* GUC 变量 */
int			DeadlockTimeout = 1000;
int			StatementTimeout = 0;
int			LockTimeout = 0;
int			IdleInTransactionSessionTimeout = 0;
int			IdleSessionTimeout = 0;
bool		log_lock_waits = false;

/* 指向该进程的 PGPROC 结构的指针（如果有的话） */
PGPROC	   *MyProc = NULL;

/*
 * 这个自旋锁保护回收的 PGPROC 结构的自由列表。
 * 我们不能使用 LWLock，因为 LWLock 管理器依赖于已经拥有 PGPROC 和等待信号量！但是这些结构相对不常用（仅在后端启动或关闭时触及），并且持续时间不长，因此自旋锁是可以的。
 */
NON_EXEC_STATIC slock_t *ProcStructLock = NULL;

/* 指向共享内存结构的指针 */
PROC_HDR   *ProcGlobal = NULL;
NON_EXEC_STATIC PGPROC *AuxiliaryProcs = NULL;
PGPROC	   *PreparedXactProcs = NULL;

/* 如果我们在等待锁，这指向相关的 LOCALLOCK */
static LOCALLOCK *lockAwaited = NULL;

static DeadLockState deadlock_state = DS_NOT_YET_CHECKED;

/* 是否有死锁检查待处理? */
static volatile sig_atomic_t got_deadlock_timeout;

static void fc_RemoveProcFromArray(int fc_code, Datum fc_arg);
static void fc_ProcKill(int fc_code, Datum fc_arg);
static void fc_AuxiliaryProcKill(int fc_code, Datum fc_arg);
static void fc_CheckDeadLock(void);


/*
 * 报告 InitProcGlobal 所需的共享内存空间。
 */
Size ProcGlobalShmemSize(void)
{
	Size		fc_size = 0;
	Size		fc_TotalProcs =
	add_size(MaxBackends, add_size(NUM_AUXILIARY_PROCS, max_prepared_xacts));

	/* ProcGlobal */
	fc_size = add_size(fc_size, sizeof(PROC_HDR));
	fc_size = add_size(fc_size, mul_size(fc_TotalProcs, sizeof(PGPROC)));
	fc_size = add_size(fc_size, sizeof(slock_t));

	fc_size = add_size(fc_size, mul_size(fc_TotalProcs, sizeof(*ProcGlobal->xids)));
	fc_size = add_size(fc_size, mul_size(fc_TotalProcs, sizeof(*ProcGlobal->subxidStates)));
	fc_size = add_size(fc_size, mul_size(fc_TotalProcs, sizeof(*ProcGlobal->statusFlags)));

	return fc_size;
}

/*
 * 报告 InitProcGlobal 所需的信号量数量。
 */
int ProcGlobalSemas(void)
{
	/*
	 * 我们需要每个后端（包括自动真空），以及每个辅助进程一个信号量。
	 */
	return MaxBackends + NUM_AUXILIARY_PROCS;
}

/*
 * InitProcGlobal -
 *	  在主进程或独立后端启动期间初始化全局进程表。
 *
 *	  我们还创建所有支持请求数量后端所需的每个进程信号量。我们过去只在后端实际启动时分配信号量，但这很糟糕，因为会导致 Postgres 在负载下失败——许多 Unix 系统（错误）配置为信号量数量较小，当尝试启动另一个后端时用尽信号量是常见故障。因此，现在我们立即在初始化时获取足够的信号量以支持所需的最大后端数量——如果系统管理员将 MaxConnections、max_worker_processes、max_wal_senders 或 autovacuum_max_workers 设置得高于其内核所支持的，他会较早发现。
 *
 *	  在这里创建信号量的另一个原因是，信号量实现通常要求我们在主进程中创建信号量，而不是在后端中。
 *
 * 注意：此函数不是由主进程下的各个后端调用的，甚至在 EXEC_BACKEND 的情况下也是如此。ProcGlobal 和 AuxiliaryProcs 指针必须为 EXEC_BACKEND 操作特殊传播。
 */
void InitProcGlobal(void)
{
	PGPROC	   *fc_procs;
	int			fc_i,
				fc_j;
	bool		fc_found;
	uint32		fc_TotalProcs = MaxBackends + NUM_AUXILIARY_PROCS + max_prepared_xacts;

	/* 创建 ProcGlobal 共享结构 */
	ProcGlobal = (PROC_HDR *)
		ShmemInitStruct("Proc Header", sizeof(PROC_HDR), &fc_found);
	Assert(!fc_found);

	/*
	 * 初始化数据结构。
	 */
	ProcGlobal->spins_per_delay = DEFAULT_SPINS_PER_DELAY;
	ProcGlobal->freeProcs = NULL;
	ProcGlobal->autovacFreeProcs = NULL;
	ProcGlobal->bgworkerFreeProcs = NULL;
	ProcGlobal->walsenderFreeProcs = NULL;
	ProcGlobal->startupBufferPinWaitBufId = -1;
	ProcGlobal->walwriterLatch = NULL;
	ProcGlobal->checkpointerLatch = NULL;
	pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PGPROCNO);
	pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PGPROCNO);

	/*
	 * 创建并初始化我们所需的所有 PGPROC 结构。有五个独立的消费者：(1)正常后端，(2)自动真空工作者和自动真空启动器，(3)后台工作者，(4)辅助进程，以及 (5)准备好的事务。每个 PGPROC 结构专门用于其中一个目的，并且它们之间不会移动。
	 */
	fc_procs = (PGPROC *) ShmemAlloc(fc_TotalProcs * sizeof(PGPROC));
	MemSet(fc_procs, 0, fc_TotalProcs * sizeof(PGPROC));
	ProcGlobal->allProcs = fc_procs;
	/* XXX allProcCount 并不是真正的全部；它排除了准备好的 xacts */
	ProcGlobal->allProcCount = MaxBackends + NUM_AUXILIARY_PROCS;

	/*
	 * 以密集的方式分配数组，镜像 PGPROC 字段。参见 PROC_HDR。
	 *
	 * XXX: 增加这些数组的填充可能是合理的，考虑到它们的访问频繁。
	 */
	ProcGlobal->xids =
		(TransactionId *) ShmemAlloc(fc_TotalProcs * sizeof(*ProcGlobal->xids));
	MemSet(ProcGlobal->xids, 0, fc_TotalProcs * sizeof(*ProcGlobal->xids));
	ProcGlobal->subxidStates = (XidCacheStatus *) ShmemAlloc(fc_TotalProcs * sizeof(*ProcGlobal->subxidStates));
	MemSet(ProcGlobal->subxidStates, 0, fc_TotalProcs * sizeof(*ProcGlobal->subxidStates));
	ProcGlobal->statusFlags = (uint8 *) ShmemAlloc(fc_TotalProcs * sizeof(*ProcGlobal->statusFlags));
	MemSet(ProcGlobal->statusFlags, 0, fc_TotalProcs * sizeof(*ProcGlobal->statusFlags));

	for (fc_i = 0; fc_i < fc_TotalProcs; fc_i++)
	{
		/* 所有 PGPROC 的通用初始化，无论类型如何。 */

		/*
		 * 设置每个 PGPROC 的信号量、闩锁和 fpInfoLock。然而，准备好的 xact 虚拟 PGPROC 不需要这些 - 它们从未与真实进程关联
		 */
		if (fc_i < MaxBackends + NUM_AUXILIARY_PROCS)
		{
			fc_procs[fc_i].sem = PGSemaphoreCreate();
			InitSharedLatch(&(fc_procs[fc_i].procLatch));
			LWLockInitialize(&(fc_procs[fc_i].fpInfoLock), LWTRANCHE_LOCK_FASTPATH);
		}
		fc_procs[fc_i].pgprocno = fc_i;

		/*
		 * 新创建的 PGPROC 结构对于正常后端、自动真空和后台工作者必须排队到适当的自由列表中。因为辅助进程的数量永远只有少数固定数量，所以在这种情况下不使用自由列表；InitAuxiliaryProcess() 使用线性搜索。准备好的事务的 PGPROC 通过 TwoPhaseShmemInit() 添加到自由列表。
		 */
		if (fc_i < MaxConnections)
		{
			/* 普通后端的 PGPROC，添加到 freeProcs 列表 */
			fc_procs[fc_i].links.next = (SHM_QUEUE *) ProcGlobal->freeProcs;
			ProcGlobal->freeProcs = &fc_procs[fc_i];
			fc_procs[fc_i].procgloballist = &ProcGlobal->freeProcs;
		}
		else if (fc_i < MaxConnections + autovacuum_max_workers + 1)
		{
			/* 自动真空启动者/工作者的 PGPROC，添加到 autovacFreeProcs 列表 */
			fc_procs[fc_i].links.next = (SHM_QUEUE *) ProcGlobal->autovacFreeProcs;
			ProcGlobal->autovacFreeProcs = &fc_procs[fc_i];
			fc_procs[fc_i].procgloballist = &ProcGlobal->autovacFreeProcs;
		}
		else if (fc_i < MaxConnections + autovacuum_max_workers + 1 + max_worker_processes)
		{
			/* 后台工作者的 PGPROC，添加到 bgworkerFreeProcs 列表 */
			fc_procs[fc_i].links.next = (SHM_QUEUE *) ProcGlobal->bgworkerFreeProcs;
			ProcGlobal->bgworkerFreeProcs = &fc_procs[fc_i];
			fc_procs[fc_i].procgloballist = &ProcGlobal->bgworkerFreeProcs;
		}
		else if (fc_i < MaxBackends)
		{
			/* walsender 的 PGPROC，添加到 walsenderFreeProcs 列表 */
			fc_procs[fc_i].links.next = (SHM_QUEUE *) ProcGlobal->walsenderFreeProcs;
			ProcGlobal->walsenderFreeProcs = &fc_procs[fc_i];
			fc_procs[fc_i].procgloballist = &ProcGlobal->walsenderFreeProcs;
		}

		/* 初始化 myProcLocks[] 共享内存队列。 */
		for (fc_j = 0; fc_j < NUM_LOCK_PARTITIONS; fc_j++)
			SHMQueueInit(&(fc_procs[fc_i].myProcLocks[fc_j]));

		/* 初始化 lockGroupMembers 列表。 */
		dlist_init(&fc_procs[fc_i].lockGroupMembers);

		/*
		 * 初始化原子变量，否则，对于当前未使用的后端，访问它们将不安全。
		 */
		pg_atomic_init_u32(&(fc_procs[fc_i].procArrayGroupNext), INVALID_PGPROCNO);
		pg_atomic_init_u32(&(fc_procs[fc_i].clogGroupNext), INVALID_PGPROCNO);
		pg_atomic_init_u64(&(fc_procs[fc_i].waitStart), 0);
	}

	/*
	 * 保存指向为辅助进程和准备好的事务保留的 PGPROC 结构块的指针。
	 */
	AuxiliaryProcs = &fc_procs[MaxBackends];
	PreparedXactProcs = &fc_procs[MaxBackends + NUM_AUXILIARY_PROCS];

	/* 创建 ProcStructLock 自旋锁 */
	ProcStructLock = (slock_t *) ShmemAlloc(sizeof(slock_t));
	SpinLockInit(ProcStructLock);
}

/*
 * InitProcess -- 为该后端初始化每个进程的数据结构
 */
void InitProcess(void)
{
	PGPROC	   *volatile *fc_procgloballist;

	/*
	 * ProcGlobal 应该已经设置好（如果我们是后端， 我们通过 fork() 或 EXEC_BACKEND 机制从 postmaster 继承
	 * 这个）。
	 */
	if (ProcGlobal == NULL)
		elog(PANIC, "proc header uninitialized");

	if (MyProc != NULL)
		elog(ERROR, "you already exist");

	/* 决定哪个列表应该提供我们的 PGPROC。 */
	if (IsAnyAutoVacuumProcess())
		fc_procgloballist = &ProcGlobal->autovacFreeProcs;
	else if (IsBackgroundWorker)
		fc_procgloballist = &ProcGlobal->bgworkerFreeProcs;
	else if (am_walsender)
		fc_procgloballist = &ProcGlobal->walsenderFreeProcs;
	else
		fc_procgloballist = &ProcGlobal->freeProcs;

	/*
	 * 尝试从适当的空闲列表获取一个 proc 结构。如果失败，
	 * 我们必须用尽 PGPROC 结构（更不用说信号量）。
	 *
	 * 当我们持有 ProcStructLock 时，还要将当前共享的 spins_per_delay 
	 * 估计值复制到本地存储。
	 */
	SpinLockAcquire(ProcStructLock);

	set_spins_per_delay(ProcGlobal->spins_per_delay);

	MyProc = *fc_procgloballist;

	if (MyProc != NULL)
	{
		*fc_procgloballist = (PGPROC *) MyProc->links.next;
		SpinLockRelease(ProcStructLock);
	}
	else
	{
		/*
		 * 如果我们到达这里，所有 PGPROCs 都在使用中。这是检测“过多后端”的
		 * 可能地点之一，因此给出标准错误消息。XXX 我们是否需要在
		 * autovacuum 案例中给出不同的失败消息？
		 */
		SpinLockRelease(ProcStructLock);
		if (am_walsender)
			ereport(FATAL,
					(errcode(ERRCODE_TOO_MANY_CONNECTIONS),
					 errmsg("number of requested standby connections exceeds max_wal_senders (currently %d)",
							max_wal_senders)));
		ereport(FATAL,
				(errcode(ERRCODE_TOO_MANY_CONNECTIONS),
				 errmsg("sorry, too many clients already")));
	}

	/*
	 * 交叉检查 PGPROC 是我们期望的类型；如果不是这样，它将被返回到错误的列表。
	 */
	Assert(MyProc->procgloballist == fc_procgloballist);

	/*
	 * 现在我们有了 PGPROC，将自己标记为一个活动的 postmaster 子进程；
	 * 这样 postmaster 可以在我们退出而不清理时检测到它。（XXX autovac 启动器目前
	 * 不参与此；可能确实应该参与。）
	 */
	if (IsUnderPostmaster && !IsAutoVacuumLauncherProcess())
		MarkPostmasterChildActive();

	/*
	 * 初始化 MyProc 的所有字段，除了之前由 InitProcGlobal 初始化的字段。
	 */
	SHMQueueElemInit(&(MyProc->links));
	MyProc->waitStatus = PROC_WAIT_STATUS_OK;
	MyProc->lxid = InvalidLocalTransactionId;
	MyProc->fpVXIDLock = false;
	MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
	MyProc->xid = InvalidTransactionId;
	MyProc->xmin = InvalidTransactionId;
	MyProc->pid = MyProcPid;
	/* backendId、databaseId 和 roleId 将稍后填写 */
	MyProc->backendId = InvalidBackendId;
	MyProc->databaseId = InvalidOid;
	MyProc->roleId = InvalidOid;
	MyProc->tempNamespaceId = InvalidOid;
	MyProc->isBackgroundWorker = !AmRegularBackendProcess();
	MyProc->delayChkptFlags = 0;
	MyProc->statusFlags = 0;
	/* 注意 - autovac 启动器故意不设置 IS_AUTOVACUUM */
	if (IsAutoVacuumWorkerProcess())
		MyProc->statusFlags |= PROC_IS_AUTOVACUUM;
	MyProc->lwWaiting = LW_WS_NOT_WAITING;
	MyProc->lwWaitMode = 0;
	MyProc->waitLock = NULL;
	MyProc->waitProcLock = NULL;
	pg_atomic_write_u64(&MyProc->waitStart, 0);
#ifdef USE_ASSERT_CHECKING
	{
		int			fc_i;

		/* 最后一个进程应该释放所有锁。 */
		for (fc_i = 0; fc_i < NUM_LOCK_PARTITIONS; fc_i++)
			Assert(SHMQueueEmpty(&(MyProc->myProcLocks[fc_i])));
	}
#endif
	MyProc->recoveryConflictPending = false;

	/* 为同步复制初始化字段 */
	MyProc->waitLSN = 0;
	MyProc->syncRepState = SYNC_REP_NOT_WAITING;
	SHMQueueElemInit(&(MyProc->syncRepLinks));

	/* 初始化用于组 XID 清理的字段。 */
	MyProc->procArrayGroupMember = false;
	MyProc->procArrayGroupMemberXid = InvalidTransactionId;
	Assert(pg_atomic_read_u32(&MyProc->procArrayGroupNext) == INVALID_PGPROCNO);

	/* 检查组锁定字段是否处于适当的初始状态。 */
	Assert(MyProc->lockGroupLeader == NULL);
	Assert(dlist_is_empty(&MyProc->lockGroupMembers));

	/* 初始化等待事件信息。 */
	MyProc->wait_event_info = 0;

	/* 初始化用于组事务状态更新的字段。 */
	MyProc->clogGroupMember = false;
	MyProc->clogGroupMemberXid = InvalidTransactionId;
	MyProc->clogGroupMemberXidStatus = TRANSACTION_STATUS_IN_PROGRESS;
	MyProc->clogGroupMemberPage = -1;
	MyProc->clogGroupMemberLsn = InvalidXLogRecPtr;
	Assert(pg_atomic_read_u32(&MyProc->clogGroupNext) == INVALID_PGPROCNO);

	/*
	 * 获取 PGPROC 的闸门的所有权，以便我们可以在其上使用 WaitLatch
	 * 。这使我们能够将进程的闸门重新指向，直到现在
	 * 指向进程本地的闸门，指向共享的闸门。
	 */
	OwnLatch(&MyProc->procLatch);
	SwitchToSharedLatch();

	/* 现在我们有了一个 proc，将等待事件报告到共享内存 */
	pgstat_set_wait_event_storage(&MyProc->wait_event_info);

	/*
	 * 我们可能在重用一个属于失败进程的信号量。因此
	 * 小心并在这里重新初始化其值。（这在严格上
	 * 已不再必要，但似乎是为了整洁而好主意。）
	 */
	PGSemaphoreReset(MyProc->sem);

	/*
	 * 安排在后端退出时进行清理。
	 */
	on_shmem_exit(fc_ProcKill, 0);

	/*
	 * 现在我们有了 PGPROC，我们可以尝试获取锁，因此初始化
	 * LWLocks 和死锁检查器所需的本地状态。
	 */
	InitLWLockAccess();
	InitDeadLockChecking();
}

/*
 * InitProcessPhase2 -- 在共享 ProcArray 中使 MyProc 可见。
 *
 * 这与 InitProcess 是分开的，因为在创建 PGPROC
 * 之前我们无法获取 LWLocks，但在 EXEC_BACKEND 的情况下 
 * ProcArrayAdd 直到我们完成 CreateSharedMemoryAndSemaphores 之后才有效。
 */
void InitProcessPhase2(void)
{
	Assert(MyProc != NULL);

	/*
	 * 将我们的 PGPROC 添加到共享内存中的 PGPROC 数组。
	 */
	ProcArrayAdd(MyProc);

	/*
	 * 安排在后端退出时清理它。
	 */
	on_shmem_exit(fc_RemoveProcFromArray, 0);
}

/*
 * InitAuxiliaryProcess -- 创建每个辅助进程的数据结构
 *
 * 这是由 bgwriter 和类似进程调用的，以便它们将拥有一个
 * 足够真实的 MyProc 值来让它们等待 LWLocks。分配的
 * PGPROC 和信号量是 InitProcGlobal 期间创建的额外一个。
 *
 * 目前不期望辅助进程等待真实（lockmgr）
 * 锁，因此我们无需设置死锁检查器。它们也不会被添加
 * 到 ProcArray 或 sinval 消息机制中。它们也不
 * 会获得分配的 VXID，因为这仅在我们实际
 * 拥有 lockmgr 锁时才有用。
 *
 * 启动进程使用锁但在正常后端意义上从不等待它们。
 * 启动进程还参与 sinval 消息作为 sendOnly 进程，因此
 * 从不读取 sinval 队列中的消息。因此 启动进程确实
 * 有一个 VXID，并且在 pg_locks 中显示。
 */
void InitAuxiliaryProcess(void)
{
	PGPROC	   *fc_auxproc;
	int			fc_proctype;

	/*
	 * ProcGlobal 应该已经设置好（如果我们是后端， 我们通过 fork() 或 EXEC_BACKEND 机制从 postmaster 继承
	 * 这个）。
	 */
	if (ProcGlobal == NULL || AuxiliaryProcs == NULL)
		elog(PANIC, "proc header uninitialized");

	if (MyProc != NULL)
		elog(ERROR, "you already exist");

	/*
	 * 我们使用 ProcStructLock 来保护
	 * AuxiliaryProcs 条目的分配和释放。
	 *
	 * 当我们持有 ProcStructLock 时，还要将当前共享的 
	 * spins_per_delay 估计值复制到本地存储。
	 */
	SpinLockAcquire(ProcStructLock);

	set_spins_per_delay(ProcGlobal->spins_per_delay);

	/*
	 * 找到一个空闲的 auxproc ... 如果没有，*大*麻烦 ...
	 */
	for (fc_proctype = 0; fc_proctype < NUM_AUXILIARY_PROCS; fc_proctype++)
	{
		fc_auxproc = &AuxiliaryProcs[fc_proctype];
		if (fc_auxproc->pid == 0)
			break;
	}
	if (fc_proctype >= NUM_AUXILIARY_PROCS)
	{
		SpinLockRelease(ProcStructLock);
		elog(FATAL, "all AuxiliaryProcs are in use");
	}

	/* 将辅助进程标记为正在被我使用 */
	/* 使用易变指针以防止代码重排 */
	((volatile PGPROC *) fc_auxproc)->pid = MyProcPid;

	MyProc = fc_auxproc;

	SpinLockRelease(ProcStructLock);

	/*
	 * 初始化 MyProc 的所有字段，除了之前由 InitProcGlobal 初始化的字段。
	 */
	SHMQueueElemInit(&(MyProc->links));
	MyProc->waitStatus = PROC_WAIT_STATUS_OK;
	MyProc->lxid = InvalidLocalTransactionId;
	MyProc->fpVXIDLock = false;
	MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
	MyProc->xid = InvalidTransactionId;
	MyProc->xmin = InvalidTransactionId;
	MyProc->backendId = InvalidBackendId;
	MyProc->databaseId = InvalidOid;
	MyProc->roleId = InvalidOid;
	MyProc->tempNamespaceId = InvalidOid;
	MyProc->isBackgroundWorker = true;
	MyProc->delayChkptFlags = 0;
	MyProc->statusFlags = 0;
	MyProc->lwWaiting = LW_WS_NOT_WAITING;
	MyProc->lwWaitMode = 0;
	MyProc->waitLock = NULL;
	MyProc->waitProcLock = NULL;
	pg_atomic_write_u64(&MyProc->waitStart, 0);
#ifdef USE_ASSERT_CHECKING
	{
		int			fc_i;

		/* 最后一个进程应该释放所有锁。 */
		for (fc_i = 0; fc_i < NUM_LOCK_PARTITIONS; fc_i++)
			Assert(SHMQueueEmpty(&(MyProc->myProcLocks[fc_i])));
	}
#endif

	/*
	 * 获取 PGPROC 的闸门的所有权，以便我们可以在其上使用 WaitLatch
	 * 。这使我们能够将进程的闸门重新指向，直到现在
	 * 指向进程本地的闸门，指向共享的闸门。
	 */
	OwnLatch(&MyProc->procLatch);
	SwitchToSharedLatch();

	/* 现在我们有了一个 proc，将等待事件报告到共享内存 */
	pgstat_set_wait_event_storage(&MyProc->wait_event_info);

	/* 检查组锁定字段是否处于适当的初始状态。 */
	Assert(MyProc->lockGroupLeader == NULL);
	Assert(dlist_is_empty(&MyProc->lockGroupMembers));

	/*
	 * 我们可能在重用一个属于失败进程的信号量。因此
	 * 小心并在这里重新初始化其值。（这在严格上
	 * 已不再必要，但似乎是为了整洁而好主意。）
	 */
	PGSemaphoreReset(MyProc->sem);

	/* 
	 * 在进程退出时安排清理。 
	 */
	on_shmem_exit(fc_AuxiliaryProcKill, Int32GetDatum(fc_proctype));
}

/* 
 * 从 bufmgr 使用，以共享 Startup 正在等待的缓冲区的值，
 * 或将值重置为“不等待” (-1)。这允许处理
 * 缓冲区引脚的恢复冲突。设置是在后端查看
 * 此值之前进行的，因此不需要锁定，特别是因为该设置是
 * 一种原子整数设置操作。
 */
void SetStartupBufferPinWaitBufId(int fc_bufid)
{
	/* 使用易变指针以防止代码重排 */
	volatile PROC_HDR *fc_procglobal = ProcGlobal;

	fc_procglobal->startupBufferPinWaitBufId = fc_bufid;
}

/* 
 * 后端在收到检查缓冲区引脚等待请求时使用。 
 */
int GetStartupBufferPinWaitBufId(void)
{
	/* 使用易变指针以防止代码重排 */
	volatile PROC_HDR *fc_procglobal = ProcGlobal;

	return fc_procglobal->startupBufferPinWaitBufId;
}

/* 
 * 检查是否至少有 N 个空闲 PGPROC 对象。
 * 
 * 注意：这是在 N 通常会很小的假设下设计的。
 */
bool HaveNFreeProcs(int fc_n)
{
	PGPROC	   *fc_proc;

	SpinLockAcquire(ProcStructLock);

	fc_proc = ProcGlobal->freeProcs;

	while (fc_n > 0 && fc_proc != NULL)
	{
		fc_proc = (PGPROC *) fc_proc->links.next;
		fc_n--;
	}

	SpinLockRelease(ProcStructLock);

	return (fc_n <= 0);
}

/* 
 * 检查当前进程是否在等待锁。 
 */
bool IsWaitingForLock(void)
{
	if (lockAwaited == NULL)
		return false;

	return true;
}

/* 
 * 在中止事务时取消任何待定的锁等待，并恢复
 * 对正在获取的锁的任何强锁计数获取。
 * 
 * （通常情况下，这只会在我们接受取消/死亡
 * 中断时发生；但是在锁等待之前或期间进行的 ereport(ERROR)
 * 也是有可能的。）
 */
void LockErrorCleanup(void)
{
	LWLock	   *fc_partitionLock;
	DisableTimeoutParams fc_timeouts[2];

	HOLD_INTERRUPTS();

	AbortStrongLockAcquire();

	/* 没有待执行的操作，如果我们没有在等待锁 */
	if (lockAwaited == NULL)
	{
		RESUME_INTERRUPTS();
		return;
	}

	/* 
	 * 如果死锁和锁超时计时器仍在运行，则关闭它们
	 * （请参见 ProcSleep）。注意，我们必须保留 LOCK_TIMEOUT
	 * 指示标志，因为此函数是在响应 SIGINT 时执行的
	 * 之前 ProcessInterrupts；否则我们将失去
	 * 知识，即 SIGINT 是由于锁超时而不是外部
	 * 来源。
	 */
	fc_timeouts[0].id = DEADLOCK_TIMEOUT;
	fc_timeouts[0].keep_indicator = false;
	fc_timeouts[1].id = LOCK_TIMEOUT;
	fc_timeouts[1].keep_indicator = true;
	disable_timeouts(fc_timeouts, 2);

	/* 从等待队列中移除自己，如果仍在队列上（可能已经不在了！） */
	fc_partitionLock = LockHashPartitionLock(lockAwaited->hashcode);
	LWLockAcquire(fc_partitionLock, LW_EXCLUSIVE);

	if (MyProc->links.next != NULL)
	{
		/* 我们可能尚未获得锁 */
		RemoveFromWaitQueue(MyProc, lockAwaited->hashcode);
	}
	else
	{
		/* 
		 * 有人已经将我们踢出了锁队列。也许他们
		 * 给予了我们锁，或者他们检测到了死锁。如果他们
		 * 确实给了我们锁，我们最好在本地锁中记住它
		 * 表。
		 */
		if (MyProc->waitStatus == PROC_WAIT_STATUS_OK)
			GrantAwaitedLock();
	}

	lockAwaited = NULL;

	LWLockRelease(fc_partitionLock);

	RESUME_INTERRUPTS();
}


/* 
 * ProcReleaseLocks() -- 在主要事务提交或中止时
 *		 释放与当前事务相关的锁
 * 
 * 在主要事务提交时，我们释放标准锁，除了会话锁。
 * 在主要事务中止时，我们释放所有锁，包括会话锁。
 * 
 * 仅在它们是事务级别时释放咨询锁；
 * 无论这是提交与否，会话级持有仍然保持。
 * 
 * 在子事务提交时，我们不释放任何锁（因此该功能根本不
 * 需要）；我们将推迟释放到父事务。
 * 在子事务中止时，我们释放子事务持有的所有锁；
 * 这通过每个锁在 ResourceOwner 机制控制下的零售释放来实现。
 */
void ProcReleaseLocks(bool fc_isCommit)
{
	if (!MyProc)
		return;
	/* 如果在等待，离开等待队列（在错误后只需需要） */
	LockErrorCleanup();
	/* 释放标准锁，包括中止时的会话级别 */
	LockReleaseAll(DEFAULT_LOCKMETHOD, !fc_isCommit);
	/* 释放事务级咨询锁 */
	LockReleaseAll(USER_LOCKMETHOD, false);
}


/* 
 * RemoveProcFromArray() -- 从共享 ProcArray 中移除此进程。 
 */
static void fc_RemoveProcFromArray(int fc_code, Datum fc_arg)
{
	Assert(MyProc != NULL);
	ProcArrayRemove(MyProc, InvalidTransactionId);
}

/* 
 * ProcKill() -- 销毁此进程的每个处理数据结构。
 * 释放其持有的 LW 锁。
 */
static void fc_ProcKill(int fc_code, Datum fc_arg)
{
	PGPROC	   *fc_proc;
	PGPROC	   *volatile *fc_procgloballist;

	Assert(MyProc != NULL);

	/* 如果通过system()等方法派生，则不安全。 */
	if (MyProc->pid != (int) getpid())
		elog(PANIC, "ProcKill() called in child process");

	/* 确保我们不在同步复制列表中 */
	SyncRepCleanupAtProcExit();

#ifdef USE_ASSERT_CHECKING
	{
		int			fc_i;

		/* 最后一个进程应该释放所有锁。 */
		for (fc_i = 0; fc_i < NUM_LOCK_PARTITIONS; fc_i++)
			Assert(SHMQueueEmpty(&(MyProc->myProcLocks[fc_i])));
	}
#endif

	/* 
	 * 释放我持有的任何 LW 锁。实际上不应该有任何，但
	 * 在我们释放我们的 PGPROC 之前再次检查并不贵…
	 */
	LWLockReleaseAll();

	/* 取消任何待定的条件变量睡眠 */
	ConditionVariableCancelSleep();

	/* 
	 * 从我们是其成员的任何锁组中分离。如果领导者
	 * 在所有其他组成员之前存在，其 PGPROC 将保持分配
	 * 直到最后一个组进程退出；该进程必须将
	 * 领导者的 PGPROC 返回到适当的列表。
	 */
	if (MyProc->lockGroupLeader != NULL)
	{
		PGPROC	   *fc_leader = MyProc->lockGroupLeader;
		LWLock	   *fc_leader_lwlock = LockHashPartitionLockByProc(fc_leader);

		LWLockAcquire(fc_leader_lwlock, LW_EXCLUSIVE);
		Assert(!dlist_is_empty(&fc_leader->lockGroupMembers));
		dlist_delete(&MyProc->lockGroupLink);
		if (dlist_is_empty(&fc_leader->lockGroupMembers))
		{
			fc_leader->lockGroupLeader = NULL;
			if (fc_leader != MyProc)
			{
				fc_procgloballist = fc_leader->procgloballist;

				/* 领导者首先退出；返回其 PGPROC。 */
				SpinLockAcquire(ProcStructLock);
				fc_leader->links.next = (SHM_QUEUE *) *fc_procgloballist;
				*fc_procgloballist = fc_leader;
				SpinLockRelease(ProcStructLock);
			}
		}
		else if (fc_leader != MyProc)
			MyProc->lockGroupLeader = NULL;
		LWLockRelease(fc_leader_lwlock);
	}

	/* 
	 * 重置 MyLatch 为进程本地的那一个。这是为了使信号
	 * 处理程序等可以在共享闩锁不再属于我们之后继续使用闩锁。
	 * 
	 * 同样，停止向 MyProc->wait_event_info 报告等待事件。
	 * 
	 * 之后清除 MyProc 并放弃共享闩锁。
	 */
	SwitchBackToLocalLatch();
	pgstat_reset_wait_event_storage();

	fc_proc = MyProc;
	MyProc = NULL;
	DisownLatch(&fc_proc->procLatch);

	fc_procgloballist = fc_proc->procgloballist;
	SpinLockAcquire(ProcStructLock);

	/* 
	 * 如果我们仍然是锁定组的成员，这意味着我们是一个领导者
	 * 在子进程之前以某种方式退出。最后剩余的子进程
	 * 将释放我们的 PGPROC。否则，现在释放它。
	 */
	if (fc_proc->lockGroupLeader == NULL)
	{
		/* 因为 lockGroupLeader 是 NULL，lockGroupMembers 应该是空的。 */
		Assert(dlist_is_empty(&fc_proc->lockGroupMembers));

		/* 返回 PGPROC 结构（和信号量）到适当的空闲列表 */
		fc_proc->links.next = (SHM_QUEUE *) *fc_procgloballist;
		*fc_procgloballist = fc_proc;
	}

	/* 更新每次延迟的旋转的共享估计 */
	ProcGlobal->spins_per_delay = update_spins_per_delay(ProcGlobal->spins_per_delay);

	SpinLockRelease(ProcStructLock);

	/* 
	 * 此进程在共享内存中不再以任何有意义的方式存在，
	 * 因此告诉主进程我们已可以接受地清理。 （XXX
	 * 自动清理启动器应该在这里包括某天）
	 */
	if (IsUnderPostmaster && !IsAutoVacuumLauncherProcess())
		MarkPostmasterChildInactive();

	/* 如果需要，唤醒自动清理启动器 -- 请参见 FreeWorkerInfo 中的注释 */
	if (AutovacuumLauncherPid != 0)
		kill(AutovacuumLauncherPid, SIGUSR2);
}

/*
 * AuxiliaryProcKill() -- 辅助进程（如 bgwriter 等）的简化版本 ProcKill
 *      仅标记 PGPROC 和信号量为未使用，不会释放它们。
 */
static void fc_AuxiliaryProcKill(int fc_code, Datum fc_arg)
{
	int			fc_proctype = DatumGetInt32(fc_arg);
	PGPROC	   *fc_auxproc PG_USED_FOR_ASSERTS_ONLY;
	PGPROC	   *fc_proc;

	Assert(fc_proctype >= 0 && fc_proctype < NUM_AUXILIARY_PROCS);

	/* 如果通过system()等方法派生，则不安全。 */
	if (MyProc->pid != (int) getpid())
		elog(PANIC, "AuxiliaryProcKill() called in child process");

	fc_auxproc = &AuxiliaryProcs[fc_proctype];

	Assert(MyProc == fc_auxproc);

	/* 释放我所持有的所有 LW 锁（见上述说明） */
	LWLockReleaseAll();

	/* 取消任何待定的条件变量睡眠 */
	ConditionVariableCancelSleep();

	/* 查看等效的 ProcKill() 代码以获取注释 */
	SwitchBackToLocalLatch();
	pgstat_reset_wait_event_storage();

	fc_proc = MyProc;
	MyProc = NULL;
	DisownLatch(&fc_proc->procLatch);

	SpinLockAcquire(ProcStructLock);

	/* 标记辅助进程为不再使用 */
	fc_proc->pid = 0;

	/* 更新每次延迟的旋转的共享估计 */
	ProcGlobal->spins_per_delay = update_spins_per_delay(ProcGlobal->spins_per_delay);

	SpinLockRelease(ProcStructLock);
}

/*
 * AuxiliaryPidGetProc -- 根据其 PID 获取辅助进程的 PGPROC
 *
 * 如果未找到则返回 NULL。
 */
PGPROC * AuxiliaryPidGetProc(int fc_pid)
{
	PGPROC	   *fc_result = NULL;
	int			fc_index;

	if (fc_pid == 0)				/* 永远不匹配虚拟 PGPROCs */
		return NULL;

	for (fc_index = 0; fc_index < NUM_AUXILIARY_PROCS; fc_index++)
	{
		PGPROC	   *fc_proc = &AuxiliaryProcs[fc_index];

		if (fc_proc->pid == fc_pid)
		{
			fc_result = fc_proc;
			break;
		}
	}
	return fc_result;
}

/*
 * ProcQueue 包：将进程置于休眠状态和唤醒它们的例程
 */

/*
 * ProcQueueAlloc -- 分配/附加到共享内存进程队列
 *
 * 返回：队列的指针
 * 副作用：如果队列之前不存在，则初始化该队列
 */
#ifdef NOT_USED
PROC_QUEUE *
ProcQueueAlloc(const char *fc_name)
{
	PROC_QUEUE *fc_queue;
	bool		fc_found;

	fc_queue = (PROC_QUEUE *)
		ShmemInitStruct(fc_name, sizeof(PROC_QUEUE), &fc_found);

	if (!fc_found)
		ProcQueueInit(fc_queue);

	return fc_queue;
}
#endif

/*
 * ProcQueueInit -- 初始化一个共享内存进程队列
 */
void ProcQueueInit(PROC_QUEUE *fc_queue)
{
	SHMQueueInit(&(fc_queue->links));
	fc_queue->size = 0;
}


/*
 * ProcSleep -- 将进程置于指定锁上的休眠状态
 *
 * 调用者必须设置 MyProc->heldLocks，以反映此进程（在所有 XIDs 下）对可加锁对象已持有的锁。
 *
 * 在入口时必须持有锁表的分区锁，并将在出口时持有。
 *
 * 结果：如果我们获得了锁，则为 PROC_WAIT_STATUS_OK；如果没有锁（死锁），则为 PROC_WAIT_STATUS_ERROR。
 *
 * 假定：在我们释放分区锁之前，没有人会干扰队列。
 *
 * 注释：进程队列现在是一个优先级队列，用于锁定。
 */
ProcWaitStatus
ProcSleep(LOCALLOCK *fc_locallock, LockMethod fc_lockMethodTable)
{
	LOCKMODE	fc_lockmode = fc_locallock->tag.mode;
	LOCK	   *fc_lock = fc_locallock->lock;
	PROCLOCK   *fc_proclock = fc_locallock->proclock;
	uint32		fc_hashcode = fc_locallock->hashcode;
	LWLock	   *fc_partitionLock = LockHashPartitionLock(fc_hashcode);
	PROC_QUEUE *fc_waitQueue = &(fc_lock->waitProcs);
	SHM_QUEUE  *fc_waitQueuePos;
	LOCKMASK	fc_myHeldLocks = MyProc->heldLocks;
	TimestampTz fc_standbyWaitStart = 0;
	bool		fc_early_deadlock = false;
	bool		fc_allow_autovacuum_cancel = true;
	bool		fc_logged_recovery_conflict = false;
	ProcWaitStatus fc_myWaitStatus;
	PGPROC	   *fc_leader = MyProc->lockGroupLeader;
	int			fc_i;

	/*
	 * 如果使用组锁，则我锁定组成员所持有的锁需要包含在 myHeldLocks 中。
	 * 这对于在组成员之间发生冲突的关系扩展锁并不是必需的。
	 * 然而，将它们包含在 myHeldLocks 中将使组成员相对于其他也试图获取这些锁的后端具有优先权。
	 * 另一方面，我们可以避免给予组成员这种锁的优先权，但似乎没有明显的优势。
	 */
	if (fc_leader != NULL)
	{
		SHM_QUEUE  *fc_procLocks = &(fc_lock->procLocks);
		PROCLOCK   *fc_otherproclock;

		fc_otherproclock = (PROCLOCK *)
			SHMQueueNext(fc_procLocks, fc_procLocks, offsetof(PROCLOCK, lockLink));
		while (fc_otherproclock != NULL)
		{
			if (fc_otherproclock->groupLeader == fc_leader)
				fc_myHeldLocks |= fc_otherproclock->holdMask;
			fc_otherproclock = (PROCLOCK *)
				SHMQueueNext(fc_procLocks, &fc_otherproclock->lockLink,
							 offsetof(PROCLOCK, lockLink));
		}
	}

	/*
	 * 确定我该在等待队列中插入的位置。
	 *
	 * 通常我应该在队列的末尾。然而，如果我已经持有与任何先前等待者请求冲突的锁，
	 * 则将我放置在第一个这样的等待者之前。这不是必要的步骤，因为死锁检测会将我移动到该等待者之前；
	 * 但立即检测这种冲突相对便宜，并避免延迟到死锁超时。
	 *
	 * 特殊情况：如果我发现自己应该在某个等待者之前，则检查我是否与已持有的锁或
	 * 在该等待者之前的请求发生冲突。如果没有，那么立即授予我请求的锁。
	 * 这与 LockAcquire 中的即时授予测试相同，除了我们只考虑在我的插入点之前的
	 * 等待队列部分。
	 */
	if (fc_myHeldLocks != 0 && fc_waitQueue->size > 0)
	{
		LOCKMASK	fc_aheadRequests = 0;
		SHM_QUEUE  *fc_proc_node;

		fc_proc_node = fc_waitQueue->links.next;
		for (fc_i = 0; fc_i < fc_waitQueue->size; fc_i++)
		{
			PGPROC	   *fc_proc = (PGPROC *) fc_proc_node;

			/*
			 * 如果我们与这个等待者属于同一锁定组，它的锁既不与我们的冲突
			 * 也不贡献于 aheadRequests。
			 */
			if (fc_leader != NULL && fc_leader == fc_proc->lockGroupLeader)
			{
				fc_proc_node = fc_proc->links.next;
				continue;
			}
			/* 他必须等我吗？ */
			if (fc_lockMethodTable->conflictTab[fc_proc->waitLockMode] & fc_myHeldLocks)
			{
				/* 我必须等他吗？ */
				if (fc_lockMethodTable->conflictTab[fc_lockmode] & fc_proc->heldLocks)
				{
					/*
					 * 是的，那么我们就有一个死锁。清理的最简单方法是调用 RemoveFromWaitQueue()，
					 * 但我们必须等到我们在等待队列上。于是，设置一个标志以便后续检查，并退出循环。
					 * 此外，记录死锁信息以备后续消息使用。
					 */
					RememberSimpleDeadLock(MyProc, fc_lockmode, fc_lock, fc_proc);
					fc_early_deadlock = true;
					break;
				}
				/* 我必须在这个等待者之前。检查特殊情况。 */
				if ((fc_lockMethodTable->conflictTab[fc_lockmode] & fc_aheadRequests) == 0 &&
					!LockCheckConflicts(fc_lockMethodTable, fc_lockmode, fc_lock,
										fc_proclock))
				{
					/* 跳过等待，直接授予自己锁。 */
					GrantLock(fc_lock, fc_proclock, fc_lockmode);
					GrantAwaitedLock();
					return PROC_WAIT_STATUS_OK;
				}
				/* 退出循环，将自己放在他前面 */
				break;
			}
			/* 不，这样就推进到下一个等待者 */
			fc_aheadRequests |= LOCKBIT_ON(fc_proc->waitLockMode);
			fc_proc_node = fc_proc->links.next;
		}

		/*
		 * 如果我们遍历了整个队列，cur 指向等待队列头部，
		 * 那么我们将像期望的那样在队列尾部插入。
		 */
		fc_waitQueuePos = fc_proc_node;
	}
	else
	{
		/* 我未持有任何锁，因此无法插队。 */
		fc_waitQueuePos = &fc_waitQueue->links;
	}

	/*
	 * 将自己插入到队列中，在上述确定的位置。
	 */
	SHMQueueInsertBefore(fc_waitQueuePos, &MyProc->links);
	fc_waitQueue->size++;

	fc_lock->waitMask |= LOCKBIT_ON(fc_lockmode);

	/* 在 PGPROC 对象中设置等待信息，也要设置 */
	MyProc->waitLock = fc_lock;
	MyProc->waitProcLock = fc_proclock;
	MyProc->waitLockMode = fc_lockmode;

	MyProc->waitStatus = PROC_WAIT_STATUS_WAITING;

	/*
	 * 如果我们检测到死锁，放弃而不等待。这必须与
	 * CheckDeadLock 的恢复代码一致。
	 */
	if (fc_early_deadlock)
	{
		RemoveFromWaitQueue(MyProc, fc_hashcode);
		return PROC_WAIT_STATUS_ERROR;
	}

	/* 标记我们正在等待锁 */
	lockAwaited = fc_locallock;

	/*
	 * 释放锁表的分区锁。
	 *
	 * 注意：这也可能导致我们退出临界区状态，可能允许接受取消/终止中断。
	 * 这是可以的，因为我们已经记录了我们正在等待锁的事实，
	 * 因此，如果发生取消/终止，LockErrorCleanup 将进行清理。
	 */
	LWLockRelease(fc_partitionLock);

	/*
	 * 此外，既然我们将成功清理在 ereport 之后，
	 * 检查是否存在与 Startup 进程的缓冲区固定死锁是安全的。
	 * 当然，只有在我们执行热备份而自己不是 Startup 进程的情况下才有必要。
	 */
	if (RecoveryInProgress() && !InRecovery)
		CheckRecoveryConflictDeadlock();

	/* 在启用超时处理程序之前重置 deadlock_state */
	deadlock_state = DS_NOT_YET_CHECKED;
	got_deadlock_timeout = false;

	
/*
	 * 设置计时器，以便我们在一段时间后唤醒并检查死锁。如果检测到死锁，处理程序会将 MyProc->waitStatus =
	 * PROC_WAIT_STATUS_ERROR，允许我们知道必须报告失败而不是成功。
	 *
	 * 通过将检查延迟到我们等待了一段时间之后，可以避免在大多数情况下运行相对较昂贵的死锁检查代码。
	 *
	 * 如果设置了 LockTimeout，也启用该超时。我们可以通过在一次调用中启用两个超时源来节省一些周期。
	 *
	 * 如果处于热备状态，我们稍后设置锁等待，以便与其他代码保持清晰。
	 */
	if (!InHotStandby)
	{
		if (LockTimeout > 0)
		{
			EnableTimeoutParams fc_timeouts[2];

			fc_timeouts[0].id = DEADLOCK_TIMEOUT;
			fc_timeouts[0].type = TMPARAM_AFTER;
			fc_timeouts[0].delay_ms = DeadlockTimeout;
			fc_timeouts[1].id = LOCK_TIMEOUT;
			fc_timeouts[1].type = TMPARAM_AFTER;
			fc_timeouts[1].delay_ms = LockTimeout;
			enable_timeouts(fc_timeouts, 2);
		}
		else
			enable_timeout_after(DEADLOCK_TIMEOUT, DeadlockTimeout);

		/*
		 * 使用为死锁超时计时器获取的当前时间作为
		 * waitStart（即，此进程开始等待锁时的时间）。由于重新获取当前时间可能会导致开销，我们
		 * 重用已获得的时间以避免该开销。
		 *
		 * 请注意，在不持有锁表的分区锁的情况下更新 waitStart，以避免通过额外的锁
		 * 获取造成开销。这可能会导致在等待开始后即使 "granted" 为 false，pg_locks 中的 "waitstart"
		 * 也会在极短的时间内变为 NULL。这在实际中是可以接受的，因为我们可以假设
		 * 用户在长时间等待锁时可能会查看 "waitstart"。
		 */
		pg_atomic_write_u64(&MyProc->waitStart,
							get_timeout_start_time(DEADLOCK_TIMEOUT));
	}
	else if (log_recovery_conflict_waits)
	{
		/*
		 * 如果启用了日志记录并且处于热备状态，则设置等待开始时间戳。
		 */
		fc_standbyWaitStart = GetCurrentTimestamp();
	}

	/*
	 * 如果在 LWLockRelease 和 WaitLatch 之间有人唤醒我们，闸门将不会等待。
	 * 但是，设置的闸门并不一定表示锁现在是空闲的，因为有许多其他来源可能设置了闸门，而不仅仅是某人释放锁。
	 *
	 * 每当闸门被设置时，我们处理中断，因此取消/死亡中断会被快速处理。
	 * 这意味着我们在这里不必担心失去对取消/死亡中断的控制。我们不会，因为在获得锁后
	 * 我们没有共享状态变化的工作要做（授予者已经完成了所有工作）。我们确实需要担心取消死锁
	 * 超时和更新 locallock 表，但如果我们对错误失去控制，LockErrorCleanup 将修复它。
	 */
	do
	{
		if (InHotStandby)
		{
			bool		fc_maybe_log_conflict =
			(fc_standbyWaitStart != 0 && !fc_logged_recovery_conflict);

			/* 设置一个定时器，并等待该定时器或锁被授予 */
			ResolveRecoveryConflictWithLock(fc_locallock->tag.lock,
											fc_maybe_log_conflict);

			/*
			 * 如果启动进程等待的时间超过 deadlock_timeout
			 * 因为锁的恢复冲突，则发出日志消息。
			 */
			if (fc_maybe_log_conflict)
			{
				TimestampTz fc_now = GetCurrentTimestamp();

				if (TimestampDifferenceExceeds(fc_standbyWaitStart, fc_now,
											   DeadlockTimeout))
				{
					VirtualTransactionId *fc_vxids;
					int			fc_cnt;

					fc_vxids = GetLockConflicts(&fc_locallock->tag.lock,
											 AccessExclusiveLock, &fc_cnt);

					/*
					 * 记录恢复冲突和持有冲突锁的后端 PID 列表。请注意，即使
					 * 此时没有此类后端，我们也会进行日志记录，因为这里的启动
					 * 进程已经等待超过 deadlock_timeout。
					 */
					LogRecoveryConflict(PROCSIG_RECOVERY_CONFLICT_LOCK,
										fc_standbyWaitStart, fc_now,
										fc_cnt > 0 ? fc_vxids : NULL, true);
					fc_logged_recovery_conflict = true;
				}
			}
		}
		else
		{
			(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
							 PG_WAIT_LOCK | fc_locallock->tag.lock.locktag_type);
			ResetLatch(MyLatch);
			/* 首先检查死锁，因为这可能值得记录 */
			if (got_deadlock_timeout)
			{
				fc_CheckDeadLock();
				got_deadlock_timeout = false;
			}
			CHECK_FOR_INTERRUPTS();
		}

		/*
		 * waitStatus 可能会异步地从 PROC_WAIT_STATUS_WAITING
		 * 更改为其他值。每次循环只读取一次以防止意外行为（例如缺少日志消息）。
		 */
		fc_myWaitStatus = *((volatile ProcWaitStatus *) &MyProc->waitStatus);

		/*
		 * 如果我们没有死锁，但在等待一个由自动真空引起的任务，则发送信号以中断它。
		 */
		if (deadlock_state == DS_BLOCKED_BY_AUTOVACUUM && fc_allow_autovacuum_cancel)
		{
			PGPROC	   *fc_autovac = GetBlockingAutoVacuumPgproc();
			uint8		fc_statusFlags;
			uint8		fc_lockmethod_copy;
			LOCKTAG		fc_locktag_copy;

			/*
			 * 获取我们需要的信息，然后立即释放锁。请注意，这种编码
			 * 意味着进程在我们有机会发送信号之前有可能终止其当前事务并
			 * 开始一个不同的事务；最坏的结果是强制回收的真空被取消。
			 * 但在任何情况下，如果我们在持有锁的情况下执行 kill()，这种情况也可能发生，
			 * 那是更不可取的。
			 */
			LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
			fc_statusFlags = ProcGlobal->statusFlags[fc_autovac->pgxactoff];
			fc_lockmethod_copy = fc_lock->tag.locktag_lockmethodid;
			fc_locktag_copy = fc_lock->tag;
			LWLockRelease(ProcArrayLock);

			/*
			 * 只有在工作者不在工作以防止 Xid 回绕的情况下才这样做。
			 */
			if ((fc_statusFlags & PROC_IS_AUTOVACUUM) &&
				!(fc_statusFlags & PROC_VACUUM_FOR_WRAPAROUND))
			{
				int			fc_pid = fc_autovac->pid;

				/* 报告此情况，如果配置为执行此操作 */
				if (message_level_is_interesting(DEBUG1))
				{
					StringInfoData fc_locktagbuf;
					StringInfoData fc_logbuf;	/* 服务器日志的错误详细信息 */

					initStringInfo(&fc_locktagbuf);
					initStringInfo(&fc_logbuf);
					DescribeLockTag(&fc_locktagbuf, &fc_locktag_copy);
					appendStringInfo(&fc_logbuf,
									 "Process %d waits for %s on %s.",
									 MyProcPid,
									 GetLockmodeName(fc_lockmethod_copy, fc_lockmode),
									 fc_locktagbuf.data);

					ereport(DEBUG1,
							(errmsg_internal("sending cancel to blocking autovacuum PID %d",
											 fc_pid),
							 errdetail_log("%s", fc_logbuf.data)));

					pfree(fc_locktagbuf.data);
					pfree(fc_logbuf.data);
				}

				/* 将自动真空工作者送回旧肯特路 */
				if (kill(fc_pid, SIGINT) < 0)
				{
					/*
					 * 这里存在竞争条件：一旦我们释放了 ProcArrayLock，
					 * 自动真空工作者可能会在我们执行 kill() 之前结束工作并退出。
					 * 因此，我们不会抱怨没有此类进程。
					 * 如果内核快速回收 PID，其他错误如 EPERM 也可能发生，
					 * 但根据经验，这样的情况似乎不太可能，因此如果我们看到
					 * 其他错误号，发出警告可能是最好的选择。
					 */
					if (errno != ESRCH)
						ereport(WARNING,
								(errmsg("could not send signal to process %d: %m",
										fc_pid)));
				}
			}

			/* 防止信号再次被发送超过一次 */
			fc_allow_autovacuum_cancel = false;
		}

		/*
		 * 如果在死锁检查中断运行后被唤醒，并且 log_lock_waits 被启用，则报告等待的情况。
		 */
		if (log_lock_waits && deadlock_state != DS_NOT_YET_CHECKED)
		{
			StringInfoData fc_buf,
						fc_lock_waiters_sbuf,
						fc_lock_holders_sbuf;
			const char *fc_modename;
			long		fc_secs;
			int			fc_usecs;
			long		fc_msecs;
			SHM_QUEUE  *fc_procLocks;
			PROCLOCK   *fc_proclock;
			bool		fc_first_holder = true,
						fc_first_waiter = true;
			int			fc_lockHoldersNum = 0;

			initStringInfo(&fc_buf);
			initStringInfo(&fc_lock_waiters_sbuf);
			initStringInfo(&fc_lock_holders_sbuf);

			DescribeLockTag(&fc_buf, &fc_locallock->tag.lock);
			fc_modename = GetLockmodeName(fc_locallock->tag.lock.locktag_lockmethodid,
									   fc_lockmode);
			TimestampDifference(get_timeout_start_time(DEADLOCK_TIMEOUT),
								GetCurrentTimestamp(),
								&fc_secs, &fc_usecs);
			fc_msecs = fc_secs * 1000 + fc_usecs / 1000;
			fc_usecs = fc_usecs % 1000;

			/*
			 * 我们循环遍历锁的 procLocks，以收集所有持有者和等待者的列表。
			 * 因此，我们能够提供更多的详细信息以用于锁调试目的。
			 *
			 * lock->procLocks 包含所有持有或等待此锁的进程。
			 */

			LWLockAcquire(fc_partitionLock, LW_SHARED);

			fc_procLocks = &(fc_lock->procLocks);
			fc_proclock = (PROCLOCK *) SHMQueueNext(fc_procLocks, fc_procLocks,
												 offsetof(PROCLOCK, lockLink));

			while (fc_proclock)
			{
				/*
				 * 如果 myProc->waitProcLock == proclock，则我们是一个等待者；
				 * 如果它为 NULL 或其他不同的值，则我们是持有者。
				 */
				if (fc_proclock->tag.myProc->waitProcLock == fc_proclock)
				{
					if (fc_first_waiter)
					{
						appendStringInfo(&fc_lock_waiters_sbuf, "%d",
										 fc_proclock->tag.myProc->pid);
						fc_first_waiter = false;
					}
					else
						appendStringInfo(&fc_lock_waiters_sbuf, ", %d",
										 fc_proclock->tag.myProc->pid);
				}
				else
				{
					if (fc_first_holder)
					{
						appendStringInfo(&fc_lock_holders_sbuf, "%d",
										 fc_proclock->tag.myProc->pid);
						fc_first_holder = false;
					}
					else
						appendStringInfo(&fc_lock_holders_sbuf, ", %d",
										 fc_proclock->tag.myProc->pid);

					fc_lockHoldersNum++;
				}

				fc_proclock = (PROCLOCK *) SHMQueueNext(fc_procLocks, &fc_proclock->lockLink,
													 offsetof(PROCLOCK, lockLink));
			}

			LWLockRelease(fc_partitionLock);

			if (deadlock_state == DS_SOFT_DEADLOCK)
				ereport(LOG,
						(errmsg("process %d avoided deadlock for %s on %s by rearranging queue order after %ld.%03d ms",
								MyProcPid, fc_modename, fc_buf.data, fc_msecs, fc_usecs),
						 (errdetail_log_plural("Process holding the lock: %s. Wait queue: %s.",
											   "Processes holding the lock: %s. Wait queue: %s.",
											   fc_lockHoldersNum, fc_lock_holders_sbuf.data, fc_lock_waiters_sbuf.data))));
			else if (deadlock_state == DS_HARD_DEADLOCK)
			{
				/*
				 * 这条消息与随后报告的错误有点重复，但在某些情况下
				 * 错误报告可能不会记录（例如，如果被异常处理程序捕获），
				 * 而我们希望确保所有长时间等待事件都被记录。
				 */
				ereport(LOG,
						(errmsg("process %d detected deadlock while waiting for %s on %s after %ld.%03d ms",
								MyProcPid, fc_modename, fc_buf.data, fc_msecs, fc_usecs),
						 (errdetail_log_plural("Process holding the lock: %s. Wait queue: %s.",
											   "Processes holding the lock: %s. Wait queue: %s.",
											   fc_lockHoldersNum, fc_lock_holders_sbuf.data, fc_lock_waiters_sbuf.data))));
			}

			if (fc_myWaitStatus == PROC_WAIT_STATUS_WAITING)
				ereport(LOG,
						(errmsg("process %d still waiting for %s on %s after %ld.%03d ms",
								MyProcPid, fc_modename, fc_buf.data, fc_msecs, fc_usecs),
						 (errdetail_log_plural("Process holding the lock: %s. Wait queue: %s.",
											   "Processes holding the lock: %s. Wait queue: %s.",
											   fc_lockHoldersNum, fc_lock_holders_sbuf.data, fc_lock_waiters_sbuf.data))));
			else if (fc_myWaitStatus == PROC_WAIT_STATUS_OK)
				ereport(LOG,
						(errmsg("process %d acquired %s on %s after %ld.%03d ms",
								MyProcPid, fc_modename, fc_buf.data, fc_msecs, fc_usecs)));
			else
			{
				Assert(fc_myWaitStatus == PROC_WAIT_STATUS_ERROR);

				/*
				 * 目前，死锁检查器总是会踢掉自己的进程，这意味着我们
				 * 只会在 deadlock_state == DS_HARD_DEADLOCK 时看到
				 * PROC_WAIT_STATUS_ERROR，并且不需要打印重复的消息。
				 * 但为了完整性和未来的适应性，如果看起来是其他人
				 * 把我们踢出了锁，则打印一条消息。
				 */
				if (deadlock_state != DS_HARD_DEADLOCK)
					ereport(LOG,
							(errmsg("process %d failed to acquire %s on %s after %ld.%03d ms",
									MyProcPid, fc_modename, fc_buf.data, fc_msecs, fc_usecs),
							 (errdetail_log_plural("Process holding the lock: %s. Wait queue: %s.",
												   "Processes holding the lock: %s. Wait queue: %s.",
												   fc_lockHoldersNum, fc_lock_holders_sbuf.data, fc_lock_waiters_sbuf.data))));
			}

			/*
			 * 在这一点上，我们可能仍然需要等待锁。重置状态，以免
			 * 再次打印上述消息。
			 */
			deadlock_state = DS_NO_DEADLOCK;

			pfree(fc_buf.data);
			pfree(fc_lock_holders_sbuf.data);
			pfree(fc_lock_waiters_sbuf.data);
		}
	} while (fc_myWaitStatus == PROC_WAIT_STATUS_WAITING);

	/*
	 * 如果计时器仍在运行，则禁用它们。与 LockErrorCleanup 一样，
	 * 我们必须保留 LOCK_TIMEOUT 指示标志：如果锁超时已经导致
	 * QueryCancelPending 被设置，我们希望取消被记录为锁超时，而不是用户取消。
	 */
	if (!InHotStandby)
	{
		if (LockTimeout > 0)
		{
			DisableTimeoutParams fc_timeouts[2];

			fc_timeouts[0].id = DEADLOCK_TIMEOUT;
			fc_timeouts[0].keep_indicator = false;
			fc_timeouts[1].id = LOCK_TIMEOUT;
			fc_timeouts[1].keep_indicator = true;
			disable_timeouts(fc_timeouts, 2);
		}
		else
			disable_timeout(DEADLOCK_TIMEOUT, false);
	}

	/*
	 * 如果锁的恢复冲突已解决，但启动进程等待的时间超过
	 * deadlock_timeout，则发出日志消息。
	 */
	if (InHotStandby && fc_logged_recovery_conflict)
		LogRecoveryConflict(PROCSIG_RECOVERY_CONFLICT_LOCK,
							fc_standbyWaitStart, GetCurrentTimestamp(),
							NULL, false);

	
/*
	 * 重新获取锁表的分区锁。我们必须这样做以在处理lockAwaited之前
	 * 持有取消/死亡中断的控制（否则我们可能会错过或重复更新locallock）。
	 */
	LWLockAcquire(fc_partitionLock, LW_EXCLUSIVE);

	/*
	 * 我们不再希望LockErrorCleanup做任何事情。
	 */
	lockAwaited = NULL;

	/*
	 * 如果我们得到了锁，确保在locallock表中记住它。
	 */
	if (MyProc->waitStatus == PROC_WAIT_STATUS_OK)
		GrantAwaitedLock();

	/*
	 * 我们不需要做其他事情，因为唤醒者已完成对锁表和MyProc的
	 * 所有必要更新。
	 */
	return MyProc->waitStatus;
}


/*
 * ProcWakeup -- 通过设置其闩锁来唤醒一个进程。
 *
 *	  还要从等待队列中移除该进程并将其链接设置为无效。
 *	  返回：等待队列中的下一个进程。
 *
 * 调用者必须持有适当的锁分区锁。
 *
 * XXX：目前，这段代码仅用于“成功”情况，并且仅对该情况
 * 正确工作。要在失败情况下清理，还需要调整锁的请求计数---
 * 参见RemoveFromWaitQueue。因此，在实践中，waitStatus参数必须为
 * PROC_WAIT_STATUS_OK。
 */
PGPROC * ProcWakeup(PGPROC *fc_proc, ProcWaitStatus fc_waitStatus)
{
	PGPROC	   *fc_retProc;

	/* 进程应该在睡眠状态... */
	if (fc_proc->links.prev == NULL ||
		fc_proc->links.next == NULL)
		return NULL;
	Assert(fc_proc->waitStatus == PROC_WAIT_STATUS_WAITING);

	/* 在我们清除列表链接之前保存下一个进程 */
	fc_retProc = (PGPROC *) fc_proc->links.next;

	/* 从等待队列中移除进程 */
	SHMQueueDelete(&(fc_proc->links));
	(fc_proc->waitLock->waitProcs.size)--;

	/*清理进程的状态并传递ok/fail信号*/
	fc_proc->waitLock = NULL;
	fc_proc->waitProcLock = NULL;
	fc_proc->waitStatus = fc_waitStatus;
	pg_atomic_write_u64(&MyProc->waitStart, 0);

	/*唤醒它*/
	SetLatch(&fc_proc->procLatch);

	return fc_retProc;
}

/*
 * ProcLockWakeup -- 在释放锁时唤醒进程的例程
 *		扫描所有等待锁的进程，唤醒任何不再被阻塞的进程。
 *
 * 调用者必须持有适当的锁分区锁。
 */ 
void ProcLockWakeup(LockMethod fc_lockMethodTable, LOCK *fc_lock)
{
	PROC_QUEUE *fc_waitQueue = &(fc_lock->waitProcs);
	int			fc_queue_size = fc_waitQueue->size;
	PGPROC	   *fc_proc;
	LOCKMASK	fc_aheadRequests = 0;

	Assert(fc_queue_size >= 0);

	if (fc_queue_size == 0)
		return;

	fc_proc = (PGPROC *) fc_waitQueue->links.next;

	while (fc_queue_size-- > 0)
	{
		LOCKMODE	fc_lockmode = fc_proc->waitLockMode;

		/*
		 * 如果(a)与早期等待者的请求不冲突，并且(b)与已持有的锁不冲突，则唤醒。
 */ 
		if ((fc_lockMethodTable->conflictTab[fc_lockmode] & fc_aheadRequests) == 0 &&
			!LockCheckConflicts(fc_lockMethodTable, fc_lockmode, fc_lock,
								fc_proc->waitProcLock))
		{
			/*可以唤醒*/
			GrantLock(fc_lock, fc_proc->waitProcLock, fc_lockmode);
			fc_proc = ProcWakeup(fc_proc, PROC_WAIT_STATUS_OK);

			/*
			 * ProcWakeup从锁的等待进程队列中移除进程
			 * 并返回链中的下一个进程; 不要使用进程的下一个链接，
			 * 因为它已经被清除。
 */ 
		}
		else
		{
			/*
			 * 无法唤醒这个家伙。记住他的请求以便后续检查。
 */ 
			fc_aheadRequests |= LOCKBIT_ON(fc_lockmode);
			fc_proc = (PGPROC *) fc_proc->links.next;
		}
	}

	Assert(fc_waitQueue->size >= 0);
}

/*
 * CheckDeadLock
 *
 * 只有在等待其他进程释放锁时，DEADLOCK_TIMEOUT超时触发时我们才会进入这个例程。检查是否存在死锁; 如果没有，就返回。 （但如果log_lock_waits为真，则通知ProcSleep记录一条消息。）如果我们有一个真正的死锁，从锁的等待队列中移除自己并向ProcSleep发出错误信号。
 */ 
static void fc_CheckDeadLock(void)
{
	int			fc_i;

	/*
	 * 对整个共享锁数据结构获取独占锁。必须按分区编号顺序获取LWLocks以避免LWLock死锁。
	 *
	 * 注意，死锁检查中断最好不要在这个进程自己持有锁分区锁的任何地方启用，否则这将永远等待。另外注意，LWLockAcquire创建一个临界区，因此这个例程无法被取消/死亡中断打断。
 */ 
	for (fc_i = 0; fc_i < NUM_LOCK_PARTITIONS; fc_i++)
		LWLockAcquire(LockHashPartitionLockByIndex(fc_i), LW_EXCLUSIVE);

	/*
	 * 检查在此期间我们是否被任何人唤醒。
	 *
	 * 如果我们被唤醒，我们可以返回并恢复我们的事务——快乐的一天。
	 * 在我们被唤醒之前释放锁的进程将其授予我们，因此我们知道我们不必再等待了。
	 *
	 * 我们通过查看是否已从等待队列中解除链接来检查。这是安全的，因为我们持有锁分区锁。
 */ 
	if (MyProc->links.prev == NULL ||
		MyProc->links.next == NULL)
		goto check_done;

#ifdef LOCK_DEBUG
	if (Debug_deadlocks)
		DumpAllLocks();
#endif

	/*运行死锁检查，并设置deadlock_state以供ProcSleep使用*/
	deadlock_state = DeadLockCheck(MyProc);

	if (deadlock_state == DS_HARD_DEADLOCK)
	{
		/*
		 * 哎呀。我们遇到了死锁。
		 *
		 * 将此进程从等待状态中脱离。（注意：我们可以通过依赖lockAwaited更有效地做到这一点，但使用这种编码保留了杀死其他事务而不是检测到死锁的事务的灵活性。）
		 *
		 * RemoveFromWaitQueue将MyProc->waitStatus设置为
		 * PROC_WAIT_STATUS_ERROR，因此ProcSleep将在我们
		 * 从信号处理程序返回后报告错误。
 */ 
		Assert(MyProc->waitLock != NULL);
		RemoveFromWaitQueue(MyProc, LockTagHashCode(&(MyProc->waitLock->tag)));

		/*
		 * 我们在这里完成了。由于ProcSleep将引发的错误导致的事务中止将导致我们持有的任何其他锁被释放，从而允许其他进程唤醒；我们在这里不需要做到这一点。注意：一个例外是释放我们持有的锁时，考虑到在我们无法获得的锁后面被阻塞的等待者的可能性，而现在可能可以被唤醒，因为我们不再在他们面前。但是，RemoveFromWaitQueue已处理唤醒任何此类进程。
 */ 
	}

	/*
	 * 并释放锁。我们以相反的顺序执行此操作，有两个原因：（1）需要多个锁的其他任何人都会尝试以递增顺序锁定它们；我们不想在其他进程可以获取它所需的所有锁之前释放其他进程。（2）这避免了LWLockRelease中的O(N^2)行为。
	 */
check_done:
	for (fc_i = NUM_LOCK_PARTITIONS; --fc_i >= 0;)
		LWLockRelease(LockHashPartitionLockByIndex(fc_i));
}

/*
 * CheckDeadLockAlert - 处理deadlock_timeout的到期。
 *
 * 注意：在信号处理程序内部运行，请小心。
 */ 
void CheckDeadLockAlert(void)
{
	int			fc_save_errno = errno;

	got_deadlock_timeout = true;

	/*
	 * 即使handle_sig_alarm已经设置了闸门，也必须再次设置闸门。那时，got_deadlock_timeout尚未设置……这不太可能成为问题，但再次设置闸门是便宜的。
	 *
	 * 注意，当此函数在procsignal_sigusr1_handler()内部运行时，处理程序函数在这里设置闸门后再次设置闸门。
 */ 
	SetLatch(MyLatch);
	errno = fc_save_errno;
}

/*
 * ProcWaitForSignal - 等待来自另一个后端的信号。
 *
 * 由于这使用了通用进程闸，因此调用者必须对无关的唤醒事件具有鲁棒性：总是检查所需状态是否发生，如果没有则再次等待。
 */ 
void ProcWaitForSignal(uint32 fc_wait_event_info)
{
	(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
					 fc_wait_event_info);
	ResetLatch(MyLatch);
	CHECK_FOR_INTERRUPTS();
}

/*
 * ProcSendSignal - 设置由pgprocno标识的后端的闸门
 */ 
void ProcSendSignal(int fc_pgprocno)
{
	if (fc_pgprocno < 0 || fc_pgprocno >= ProcGlobal->allProcCount)
		elog(ERROR, "pgprocno out of range");

	SetLatch(&ProcGlobal->allProcs[fc_pgprocno].procLatch);
}

/*
 * BecomeLockGroupLeader - 指定进程作为锁组首领
 *
 * 一旦此函数返回，其他进程可以通过调用BecomeLockGroupMember来加入锁组。
 */ 
void BecomeLockGroupLeader(void)
{
	LWLock	   *fc_leader_lwlock;

	/*如果我们已经做到这一点，就不需要再做一次。*/
	if (MyProc->lockGroupLeader == MyProc)
		return;

	/*我们最好不要成为追随者。*/
	Assert(MyProc->lockGroupLeader == NULL);

	/*创建单成员组，仅包含我们自己。*/
	fc_leader_lwlock = LockHashPartitionLockByProc(MyProc);
	LWLockAcquire(fc_leader_lwlock, LW_EXCLUSIVE);
	MyProc->lockGroupLeader = MyProc;
	dlist_push_head(&MyProc->lockGroupMembers, &MyProc->lockGroupLink);
	LWLockRelease(fc_leader_lwlock);
}

/*
 * BecomeLockGroupMember - 指定进程作为锁组成员
 *
 * 这相当简单，除了可能的领导者在我们设法加入之前可能退出的情况；而PGPROC可能会被回收用于无关的进程。为了避免这种情况，我们要求调用者传递所需PGPROC的PID作为互锁。如果我们成功加入了所需的锁组，则返回true；否则返回false。
 */ 
bool BecomeLockGroupMember(PGPROC *fc_leader, int fc_pid)
{
	LWLock	   *fc_leader_lwlock;
	bool		fc_ok = false;

	
/* 组长不能成为组的成员 */
	Assert(MyProc != fc_leader);

	/* 不能已经是组的成员 */
	Assert(MyProc->lockGroupLeader == NULL);

	/* PID 必须是有效的。 */
	Assert(fc_pid != 0);

	/* 
	 * 获取保护组字段的锁。注意 LockHashPartitionLockByProc
	 * 访问 leader->pgprocno 在一个可能是空闲的 PGPROC 中。这个是安全的
	 * 因为所有 PGPROCs 的 pgprocno 字段在共享内存
	 * 初始化期间被设置，之后从不改变；因此我们将获得
	 * 正确的锁，即使 leader PGPROC 正在被回收的过程中。
	 */
	fc_leader_lwlock = LockHashPartitionLockByProc(fc_leader);
	LWLockAcquire(fc_leader_lwlock, LW_EXCLUSIVE);

	/* 这是我们寻找的组长吗？ */
	if (fc_leader->pid == fc_pid && fc_leader->lockGroupLeader == fc_leader)
	{
		/* 好的，加入组 */
		fc_ok = true;
		MyProc->lockGroupLeader = fc_leader;
		dlist_push_tail(&fc_leader->lockGroupMembers, &MyProc->lockGroupLink);
	}
	LWLockRelease(fc_leader_lwlock);

	return fc_ok;
}
